<

インサイド flutter

このドキュメントでは、Flutter ツールキットの内部動作について説明します。 FlutterのAPIが可能。 Flutter ウィジェットはアグレッシブなメソッドを使用して構築されているため、 Flutter で構築されたユーザー インターフェイスには、多数の ウィジェット。このワークロードをサポートするために、Flutter はサブリニア アルゴリズムを使用して、 レイアウトと構築ウィジェット、およびツリーを作成するデータ構造 手術が効率的であり、多くの定数係数の最適化が行われています。 いくつかの追加の詳細により、このデザインはまた、開発者向けのy 無限スクロール リストを作成するコールバックを使用して、無限スクロール リストを作成します。 ユーザーに表示されるウィジェット。

積極的な構成可能性

Flutter の最も特徴的な側面の 1 つは、攻撃的 構成可能性。ウィジェットは他のウィジェットを組み合わせて構築されます。 これら自体は、徐々に基本的なウィジェットから構築されています。 例えば、Padding他のウィジェットのプロパティではなく、ウィジェットです。 その結果、Flutter で構築されたユーザー インターフェイスは、多くの要素で構成されます。 多くのウィジェット。

ウィジェット構築の再帰は底に達しますRenderObjectWidgets、 基礎となるノードを作成するウィジェットです。与える木。 レンダー ツリーは、ユーザーのジオメトリを保存するデータ構造です。 インターフェイス。これは実行中に計算されます。レイアウトそしてその間に使用されましたペインティングヒットテスト。ほとんどの Flutter 開発者は、レンダリング オブジェクトを直接作成しません。 その代わりに、ウィジェットを使用してレンダー ツリーを操作します。

ウィジェット層での積極的な構成可能性をサポートするために、 Flutter は、多くの効率的なアルゴリズムと最適化を使用します。 ウィジェット レイヤーとレンダー ツリー レイヤーの両方について説明します。 以下のサブセクション。

サブリニアレイアウト

多数のウィジェットとレンダリング オブジェクトを使用すると、優れたパフォーマンスを実現するための鍵となります。 パフォーマンスは効率的なアルゴリズムです。最も重要なのは、 のパフォーマンスレイアウトを決定するアルゴリズムです。 レンダー オブジェクトのジオメトリ(サイズや位置など)。 他の一部のツールキットでは、O(N²) 以下のレイアウト アルゴリズムが使用されています。 (たとえば、ある制約領域での固定小数点反復)。 Flutter は初期レイアウトのリニアなパフォーマンスを目指しており、サブリニア レイアウトのパフォーマンス後で更新する一般的なケースでは、 既存のレイアウト。通常、レイアウトに費やす時間は、 レンダリング オブジェクトの数よりもゆっくりとスケールします。

Flutter はフレームごとに 1 つのレイアウトを実行し、レイアウト アルゴリズムが機能します ワンパスで。制約親から木に受け継がれる オブジェクトは、それぞれの子に対してレイアウト メソッドを呼び出します。 子は独自のレイアウトを再帰的に実行してから戻ります。幾何学レイアウト メソッドから戻ってツリーを上に移動します。重要なことは、 レンダリング オブジェクトがそのレイアウト メソッドから返されると、そのレンダリングは オブジェクトは再び訪問されません1次のフレームのレイアウトまで。このアプローチは、可能性のあるものを組み合わせます。 それ以外の場合は、メジャー パスとレイアウト パスが 1 つのパスに分割され、 その結果、各レンダー オブジェクトがアクセスされます。せいぜい 二度2レイアウト中:途中で1回 木を下り、途中で一度木に登ります。

Flutter には、この一般的なプロトコルをいくつか特殊化したものがあります。 最も一般的な専門分野は次のとおりです。RenderBoxで動作します。 2 次元のデカルト座標。ボックス レイアウトでは、制約は 最小幅と最大幅、最小高さおよび最大高さです。レイアウト中に、 子は、これらの境界内のサイズを選択することによってそのジオメトリを決定します。 子がレイアウトから戻った後、親は子のレイアウトを決定します。 親の座標系での位置3。 子のレイアウトはその位置に依存できないことに注意してください。 子供が生まれるまでポジションが決まらないから レイアウトから戻ります。その結果、親は自由に位置を変更できます。 子はレイアウトを再計算する必要がありません。

より一般的には、レイアウト中に、それだけから流れてくる情報 親から子への制約とそれだけという情報 子から親への流れがジオメトリです。これらの不変条件により、 レイアウト中に必要な作業量:

  • 子が自分のレイアウトをダーティとしてマークしていない場合、子は次のことを行うことができます。 レイアウトからすぐに戻り、ウォークを切断します。 親は子に、子が受け取ったのと同じ制約を与えます。 前のレイアウトのとき。

  • 親が子のレイアウト メソッドを呼び出すたびに、親は次のことを示します。 子から返されたサイズ情報を使用するかどうか。もしも、 よくあることですが、親はサイズ情報を使用しません。 子が選択した場合、親はレイアウトを再計算する必要はありません。 親は新しいサイズが保証されているため、新しいサイズになります。 既存の制約に準拠します。

  • きつい制約とは、1 つだけ満たすことができる制約です。 有効なジオメトリ。たとえば、最小幅と最大幅が次の場合、 最小高さと最大高さが互いに等しい、 これらの制約を満たす唯一のサイズは、 幅と高さ。親が厳しい制約を提供している場合、 そうすれば、子が実行されるたびに、親はレイアウトを再計算する必要がなくなります。 親が子のサイズを使用している場合でも、レイアウトを再計算します。 子はレイアウトでサイズを変更できないため、新しいものが出た 親からの制約。

  • レンダー オブジェクトは、提供された制約を使用することを宣言できます。 親はそのジオメトリを決定するためだけに使用します。そのような宣言 そのレンダリング オブジェクトの親が行うことをフレームワークに通知します。 子がレイアウトを再計算するときに、そのレイアウトを再計算する必要がありません。たとえ制約が厳しくなくてもたとえ親の レイアウトはお子様のサイズによって異なります、子供は変えられないから 親からの新しい制約がない場合のサイズ。

これらの最適化の結果、レンダー オブジェクト ツリーに次のものが含まれる場合、 ダーティ ノード、それらのノードとその周囲のサブツリーの限られた部分のみ これらはレイアウト中にアクセスされます。

サブリニアウィジェットの構築

レイアウト アルゴリズムと同様に、Flutter のウィジェット構築アルゴリズム サブリニアです。構築された後、ウィジェットはエレメント 木、ユーザー インターフェイスの論理構造が保持されます。 ウィジェット自体は次のとおりであるため、要素ツリーが必要です。不変これは、(とりわけ)自分のことを思い出せないことを意味します。 他のウィジェットとの親または子関係。要素ツリーも を保持しますステートフル ウィジェットに関連付けられたオブジェクト。

ユーザー入力 (またはその他の刺激) に応答して、要素がダーティになる可能性があります。 たとえば、開発者が電話した場合setState()関連する状態について 物体。フレームワークはダーティ要素のリストを保持し、直接ジャンプします その間彼らに建てるフェーズ、クリーンな要素をスキップします。その間 構築フェーズ、情報の流れ一方向に要素の下へ ツリー。これは、ビルド中に各要素が最大 1 回アクセスされることを意味します。 段階。一度洗浄した要素は再び汚れることはありません。 帰納法により、その祖先要素もすべて 綺麗4

なぜなら、ウィジェットは不変、要素がそれ自体をマークしていない場合 ダーティな場合、要素はビルドからすぐに戻り、ウォークを切断することができます。 親が同一のウィジェットを使用して要素を再構築する場合。さらに、 要素は 2 つのウィジェットのオブジェクト ID を比較するだけで済みます。 新しいウィジェットが次のウィジェットと同じであることを確認するための参照 古いウィジェット。開発者はこの最適化を利用して、再投影ウィジェットに事前構築された子が含まれるパターン ウィジェットはビルド時にメンバー変数として保存されます。

ビルド中、Flutter は次を使用して親チェーンをたどることも避けます。InheritedWidgets。ウィジェットが通常、親チェーンをたどる場合、 たとえば、現在のテーマの色、ビルドフェーズを決定する場合 ツリーの深さでは O(N²) になりますが、これは非常に大きくなる可能性があります。 攻撃的な構成により大きい。こうした親の散歩を避けるためには、 フレームワークは、メンテナンスによって情報を要素ツリーにプッシュします。 のハッシュテーブルInheritedWidget各要素の s。通常、多くの 要素は同じハッシュ テーブルを参照し、そのハッシュ テーブルは次の時点でのみ変更されます。 新しいものを導入する要素InheritedWidget

線形調整

一般に信じられていることに反して、Flutter はツリー差分を使用しません。 アルゴリズム。代わりに、フレームワークは要素を再利用するかどうかを決定します。 O(N) を使用して各要素の子リストを個別に検査する アルゴリズム。子リスト調整アルゴリズムは、 以下の場合:

  • 古い子リストは空です。
  • 2 つのリストは同一です。
  • 正確に 1 つ以上のウィジェットの挿入または削除があります リストの 1 か所に。
  • 各リストに同じウィジェットが含まれている場合 鍵5、2 つのウィジェットが一致します。

一般的なアプローチは、両方の子の始まりと終わりを一致させることです。 各ウィジェットのランタイムタイプとキーを比較してリストを表示します。 各リストの中央に空ではない範囲が見つかる可能性があります これには、一致しない子がすべて含まれます。フレームワークは次に、 古い子リストの範囲内の子をハッシュ テーブルに格納します 彼らのキーに基づいて。次に、フレームワークは新しい範囲を歩きます。 子リストを取得し、一致するキーによってハッシュ テーブルをクエリします。比類のない 子供たちは捨てられて再建されるマッチした子を最初から作成する 新しいウィジェットを使用して再構築されます。

樹木の手術

要素は独自のものであるため、要素を再利用することはパフォーマンスにとって重要です。 データの 2 つの重要な部分: ステートフル ウィジェットの状態と 基礎となるレンダリング オブジェクト。フレームワークが要素を再利用できる場合、 ユーザー インターフェイスの論理部分の状態が保存されます。 以前に計算されたレイアウト情報は再利用できます。 多くの場合、サブツリー全体のウォークを回避します。実際、要素を再利用すると、 Flutter がサポートするほど価値がある非地元の木の突然変異 状態とレイアウト情報を保持します。

開発者は、GlobalKeyウィジェットの 1 つを使用します。各グローバル キーは、全体を通じて一意です。 アプリケーション全体がスレッド固有のハッシュ テーブルに登録されます。 ビルドフェーズ中に、開発者はグローバルウィジェットを使用してウィジェットを移動できます。 要素ツリー内の任意の場所へのキー。建物を建てるのではなく、 その場所に新しい要素がある場合、フレームワークはハッシュをチェックします table を作成し、既存の要素を以前の場所から親に戻します。 サブツリー全体を保持したまま、新しい場所に移動します。

親が再設定されたサブツリー内のレンダー オブジェクトは、 レイアウト制約が唯一の情報であるため、レイアウト情報 レンダー ツリー内で親から子に流れる情報。 新しい親は、その子リストに次のものがあるため、レイアウトに対してダーティとしてマークされます。 変更されましたが、新しい親が同じレイアウトを子に渡す場合 子が古い親から受け取った制約に対して、子は次のことを行うことができます。 レイアウトからすぐに戻り、歩行を中断します。

グローバル キーと非ローカル ツリーの突然変異は、次のような分野で広く使用されています。 開発者は、ヒーローのトランジションやナビゲーションなどの効果を実現できます。

定数因子最適化

これらのアルゴリズムの最適化に加えて、積極的な 構成可能性は、いくつかの重要な定数要素にも依存します。 最適化。これらの最適化は、基本的な部分で最も重要です。 上で説明した主要なアルゴリズム。

  • 子モデルにとらわれない。子リストを使用するほとんどのツールキットとは異なり、 Flutter のレンダー ツリーは、特定の子モデルにコミットしません。 たとえば、RenderBoxクラスには要約がありますvisitChildren()具体的ではなく方法論firstChildnextSiblingインターフェース。 多くのサブクラスは、メンバーとして直接保持される 1 つの子のみをサポートします 子のリストではなく、変数を使用します。例えば、RenderPadding単一の子のみをサポートするため、レイアウトがより単純になります 実行にかかる時間が短いメソッド。

  • 視覚的なレンダー ツリー、論理的なウィジェット ツリー。Flutter では、レンダリング ツリーはデバイスに依存しない視覚座標系で動作します。 これは、X 座標のより小さい値が常に の方向にあることを意味します。 現在の読み取り方向が右から左であっても、左に進みます。 ウィジェット ツリーは通常、論理座標で動作します。 と始める終わり視覚的な解釈に依存する値 読む方向について。ロジカルからビジュアルへの変革 座標は、ウィジェット ツリーと レンダーツリー。このアプローチは、レイアウトと レンダー ツリーでのペイント計算は、レンダー ツリーでのペイント計算よりも頻繁に発生します。 ウィジェットからレンダリング ツリーへのハンドオフが行われ、座標変換の繰り返しを回避できます。

  • 特殊なレンダリング オブジェクトによって処理されるテキスト。大多数が レンダリング オブジェクトの多くはテキストの複雑さを知りません。その代わり、 テキストは特殊なレンダリング オブジェクトによって処理されます。RenderParagraph、 これはレンダー ツリーの葉です。をサブクラス化するのではなく、 テキスト認識レンダー オブジェクト。開発者はテキストをレンダリング オブジェクトに組み込みます。 コンポジションを使用したユーザー インターフェイス。このパターンが意味するのは、RenderParagraph親が指定している限り、テキスト レイアウトの再計算を回避できます。 同じレイアウト制約があり、これは樹木の手術中であっても共通です。

  • 観測可能なオブジェクト。Flutter はモデル観察とモデル観察の両方を使用します。 リアクティブパラダイム。明らかに、反応的なパラダイムが支配的ですが、 ただし、Flutter は一部のリーフ データ構造に監視可能なモデル オブジェクトを使用します。 例えば、Animation■ 値が変更されたときにオブザーバ リストに通知します。 Flutter は、これらの監視可能なオブジェクトをウィジェット ツリーから レンダー ツリーはそれらを直接監視し、 変更されたときのパイプラインの適切な段階。例えば、 への変更Animation<Color>ペイントフェーズのみをトリガーする可能性があります 構築段階とペイント段階の両方ではなく。

アグレッシブな活動によって生み出された大きな木々をまとめて合計します。 これらの最適化は、パフォーマンスに大きな影響を与えます。

Element ツリーと RenderObject ツリーの分離

RenderObjectElementFlutter の (ウィジェット) ツリーは同型です (厳密に言えば、RenderObjectツリーはのサブセットですElement木)。明らかな単純化は、これらのツリーを次のように結合することです。 一本の木。ただし、実際には、これを使用すると多くの利点があります。 これらのツリーは別のものです。

  • パフォーマンス。レイアウトを変更すると、該当部分のみが表示されます。 レイアウト ツリーをたどる必要があります。構成要素により、 ツリーには、スキップする必要がある追加のノードが多数含まれることがよくあります。

  • 明瞭さ。懸念事項をより明確に分離することで、ウィジェットを使用できるようになります。 プロトコルとレンダー オブジェクト プロトコルはそれぞれに特化されています。 特定のニーズに対応し、API サーフェスを簡素化し、コストを削減します。 バグのリスクとテストの負担。

  • タイプセーフティ。レンダー オブジェクト ツリーは、よりタイプ セーフになります。 実行時に子が適切な型であることを保証できます (各座標系には、独自のタイプのレンダリング オブジェクトがあります)。 構成ウィジェットは、使用される座標系にとらわれないことができます レイアウト中(たとえば、同じウィジェットがアプリの一部を公開する) モデルはボックス レイアウトとスライバー レイアウトの両方で使用できます)。 要素ツリーでレンダー オブジェクトのタイプを確認するには、 木の散歩。

無限スクロール

無限にスクロールするリストは、ツールキットにとって難しいことで知られています。 Flutter はシンプルなインターフェースで無限スクロールリストをサポートします に基づくビルダーパターンでは、ListViewコールバックを使用します ウィジェットがユーザーに表示されるときにオンデマンドで構築します。 スクロール。この機能をサポートするには以下が必要ですビューポート対応のレイアウトオンデマンドでウィジェットを構築する

ビューポート対応のレイアウト

Flutter のほとんどのものと同様、スクロール可能なウィジェットは次を使用して構築されます。 構成。スクロール可能なウィジェットの外側はViewport、 これは「内側が大きい」箱、つまりその子を意味します ビューポートの境界を超えて拡張することができ、ビューポート内にスクロールすることもできます。 意見。ただし、持っているのではなく、RenderBox子、ビューポートにはRenderSliverとして知られる子供たち細片、ビューポートを認識する機能を備えています。 レイアウトプロトコル。

スライバー レイアウト プロトコルはボックス レイアウトの構造と一致します 親が子に制約を渡すというプロトコル。 代わりにジオメトリを受け取ります。ただし、コンストレイントとジオメトリ データは 2 つのプロトコル間で異なります。スライバープロトコルでは、子供たちは の量など、ビューポートに関する情報が与えられます。 目に見える残りのスペース。返されるジオメトリ データにより、 折りたたみ可能なヘッダーや 視差。

異なるスライバーが、ビューポート内の利用可能なスペースを異なる形式で埋めます。 方法。たとえば、子の線形リストを生成するスライバーは次のようになります。 スライバーがなくなるか、子供がなくなるまで、各子供を順番に取り出します。 スペースが足りなくなります。同様に、二次元を生み出すスライバー 子のグリッドは、表示されているグリッドの部分のみを塗りつぶします。 彼らはどのくらいのスペースが表示されているかを認識しているため、断片が生成する可能性があります たとえ子供を産む可能性があるとしても、子供の数は有限です。e 無限の数の子供たち。

スライバーを構成して、特注のスクロール可能なレイアウトと効果を作成できます。 たとえば、単一のビューポートに、その後に折りたたみ可能なヘッダーを含めることができます。 線形リスト、次にグリッドによる。 3 つのスライバーすべてが協力します。 実際に存在する子のみを生成するスライバー レイアウト プロトコル それらの子が属しているかどうかに関係なく、ビューポートを通して表示されます。 ヘッダー、リスト、またはグリッドに6

オンデマンドでウィジェットを構築する

Flutter が厳密な場合ビルドしてレイアウトしてペイントするパイプライン、 上記では無限スクロールを実装するには不十分です。 リストを通して見えるスペースの量に関する情報のため ビューポートはレイアウト段階でのみ使用できます。それなし 追加の機械を構築するにはレイアウト段階が遅すぎます。 スペースを埋めるために必要なウィジェット。 Flutter はこの問題を解決します パイプラインのビルドとレイアウトのフェーズをインターリーブすることによって。いずれにおいても レイアウト段階の時点で、フレームワークは新しい構築を開始できます。 オンデマンドのウィジェットそれらのウィジェットが 現在レイアウトを実行しているレンダリングオブジェクト

インターリーブ ビルドとレイアウトは、厳格なルールがあるからこそ可能です。 ビルドおよびレイアウトのアルゴリズムにおける情報の伝播を制御します。 具体的には、構築フェーズでは、情報のみが伝播されます。 木の下へ。レンダーオブジェクトがレイアウトを実行しているとき、レイアウトは walk はそのレンダー オブジェクトの下のサブツリーを訪問していません。つまり、 そのサブツリー内で構築することによって生成された書き込みは、何も無効にすることはできません。 これまでにレイアウト計算に入力された情報。同様に、 一度置きますレンダー オブジェクトから返された場合、そのレンダー オブジェクトは このレイアウト中は二度とアクセスされることはありません。つまり、書き込みは行われません。 後続のレイアウト計算によって生成される、 レンダー オブジェクトのサブツリーを構築するために使用される情報。

さらに、線形調整とツリー手術が不可欠です スクロール中に要素を効率的に更新したり変更したりするため 要素がスクロールされて表示されたり表示されなくなったりするときのレンダー ツリー ビューポートの端。

API 人間工学

高速であることは、フレームワークが実際に効果的に使用できる場合にのみ重要です。 Flutter の API 設計をより使いやすくするために、Flutter は 開発者との大規模な UX 研究で繰り返しテストされました。これらの研究 時には既存の設計決定を確認し、時にはガイドに役立ちました 機能の優先順位付けを行い、場合によっては方向性を変更しました。 APIの設計。たとえば、Flutter の API は大量に文書化されています。 UX 研究ではそのような文書の価値が確認されましたが、強調表示された 特にサンプルコードと説明図が必要です。

このセクションでは、Flutter の API 設計で行われた決定のいくつかについて説明します 使いやすさを助けるために。

開発者の考え方に合わせて API を特化する

Flutter のノードの基本クラスWidgetElement、 とRenderObjectツリーは子モデルを定義しません。これにより、各ノードが そのノードに適用できる子モデルに特化したものです。

多くのWidgetオブジェクトには 1 つの子がありますWidgetしたがって、公開するだけです 独身者childパラメータ。一部のウィジェットは任意の数をサポートします。 子供たち、そしてchildrenリストを取るパラメータ。 一部のウィジェットには子がまったくなく、メモリも予約されません。 パラメータはありません。同様に、RenderObjectsAPIを公開する 子モデルに固有の。RenderImageはリーフノードであり、 子供の概念。RenderPadding子供が一人いるので収納力あり 単一の子への単一のポインターの場合。RenderFlex任意の値を取る 子の数をリンクリストとして管理します。

まれに、より複雑な子モデルが使用されることがあります。のRenderTableレンダリング オブジェクトのコンストラクターは、次の配列の配列を受け取ります。 子、クラスは数値を制御するゲッターとセッターを公開します。 行と列の数が異なり、置換するための特定の方法があります。 X、Y 座標による個々の子、行を追加し、 子の配列の新しい配列、および子のリスト全体を置き換える 単一の配列と列数を使用します。実装では、 オブジェクトは、ほとんどのレンダー オブジェクトのようにリンク リストを使用しませんが、 代わりにインデックス可能な配列を使用します。

ChipウィジェットとInputDecorationオブジェクトには一致するフィールドがあります 関連するコントロールに存在するスロット。ワンサイズですべてに対応できる場所 子モデルは、セマンティクスを強制的にリストの上に重ねます。 たとえば、最初の子をプレフィックス値として定義する子。 2 番目のサフィックスは、専用の子モデルにより次のことが可能になります。 代わりに使用される専用の名前付きプロパティ。

この柔軟性により、これらのツリー内の各ノードを次の方法で操作できます。 その役割にとって最も慣用的な方法です。セルを挿入することはまれです テーブル内では、他のすべてのセルが回り込むことになります。同様に、 代わりにインデックスを使用してフレックス行から子を削除することはまれです。 参照により。

RenderParagraphobject は最も極端なケースです。オブジェクトには次の子があります。 全く違うタイプで、TextSpan。でRenderParagraph境界、 のRenderObject木が変化してTextSpan木。

開発者のニーズを満たすために API を特化する全体的なアプローチ 期待値は子モデル以外にも適用されます。

いくつかのかなり些細なウィジェットが特別に存在するため、開発者は 問題の解決策を探すときにそれらが見つかります。を追加する 行または列へのスペースの挿入は、方法がわかれば簡単に行うことができます。 のExpandedウィジェットとサイズゼロSizedBox子供ですが、発見中です を検索するため、そのパターンは不要ですspaceを明らかにしますSpacerウィジェット、使用するExpandedSizedBox直接 効果を達成するために。

同様に、ウィジェットのサブツリーを非表示にすることも、 ビルド内のウィジェット サブツリーはまったくありません。ただし、開発者は通常、次のことを期待しています。 これを行うためのウィジェットが必要です。Visibilityウィジェットが存在します このパターンを簡単な再利用可能なウィジェットでラップします。

明示的な引数

UI フレームワークには多くのプロパティがある傾向があります。 各コンストラクターの意味論的な意味を思い出せることはほとんどありません 各クラスの引数。 Flutter はリアクティブ パラダイムを使用するため、 Flutter のビルド メソッドには多くの呼び出しがあるのが一般的です。 コンストラクター。 Dart の名前付き引数のサポートを活用することで、 Flutter の API は、そのようなビルド メソッドを明確で理解しやすいものに保つことができます。

このパターンは、複数の引数を持つメソッドに拡張されます。 特に、任意のブール引数に拡張されるため、分離されます。trueまたfalseメソッド呼び出し内のリテラルは常に自己文書化されます。 さらに、二重否定によって一般的に引き起こされる混乱を避けるために API では、ブール引数とプロパティは常に 肯定形 (たとえば、enabled: trueそれよりもdisabled: false)。

落とし穴を乗り越える

Flutter フレームワークのさまざまな場所で使用されるテクニックは次のとおりです。 エラー条件が存在しないように API を定義します。これにより削除されます 考慮されたエラーのクラス全体。

たとえば、補間関数により、一方または両方の端が可能になります。 補間をエラーケースとして定義するのではなく、null にします。 2 つの null 値の間の補間は常に null であり、補間 null 値からまたは null 値への変換は、補間と同等です。 指定された型のゼロアナログに変換します。これは、開発者が 誤って補間関数に null を渡してもヒットしません。 エラーの場合がありますが、代わりに適切な結果が得られます。

より巧妙な例は次のとおりです。Flexレイアウトアルゴリズム。の概念 このレイアウトでは、フレックス レンダー オブジェクトに与えられるスペースが 子間で分割されるため、フレックスのサイズは次のようになります。 利用可能なスペース全体。オリジナルのデザインで、 無限スペースは失敗します。これは、フレックスが次のとおりである必要があることを意味します。 サイズが無限で、無駄なレイアウト構成。代わりに、API 無限のスペースがフレックスに割り当てられるように調整されました レンダー オブジェクトの場合、レンダー オブジェクトは目的のサイズに合わせてサイズを調整します。 サイズ子の数を減らし、エラーが発生する可能性のあるケースの数を減らします。

このアプローチは、次のことを可能にするコンストラクターの存在を避けるためにも使用されます。 不整合なデータが作成される。たとえば、PointerDownEventコンストラクターでは許可されていませんdownの財産PointerEventに に設定されるfalse(自己矛盾となる状況)。 代わりに、コンストラクターには、downフィールドに入力し、常に次のように設定します。true

一般に、アプローチはすべてのオブジェクトに対して有効な解釈を定義することです。 入力ドメインの値。最も単純な例は次のとおりですColorコンストラクタ。 4 つの整数 (赤に 1 つ、緑に 1 つ) を取得する代わりに、 1 つは青用、もう 1 つはアルファ用で、それぞれが範囲外である可能性があります。 デフォルトのコンストラクターは単一の整数値を受け取り、以下を定義します。 各ビットの意味 (たとえば、下位 8 ビットは、 赤コンポーネント) なので、入力値はすべて有効な色の値になります。

より複雑な例は次のとおりですpaintImage()関数。この機能 は 11 個の引数を取り、その中には非常に広い入力ドメインを持つものもありますが、 互いにほぼ直交するように慎重に設計されており、 無効な組み合わせはほとんどありません。

エラーケースを積極的に報告する

すべてのエラー状態を設計できるわけではありません。残った人たちにとっては、 デバッグ ビルドでは、Flutter は通常、エラーを非常にキャッチしようとします。 早めに、すぐに報告してください。アサートは広く使用されています。 コンストラクターの引数は詳細に健全性チェックされます。ライフサイクルとは、 監視されており、不一致が検出されるとすぐに監視されます。 例外がスローされる原因となります。

場合によっては、これが極端に行われることがあります。たとえば、ランニング中などです。 単体テストでは、テストで他に何を行っているかに関係なく、RenderBoxレイアウトされたサブクラスは、その固有のサブクラスかどうかを積極的に検査します。 サイジング方法は、本質的なサイジング規約を満たします。これはキャッチに役立ちます 他の方法では実行できない可能性がある API のエラー。

例外がスローされる場合、例外には次の情報が含まれます。 利用可能です。 Flutter のエラー メッセージの一部は、 関連するスタック トレースを使用して、最も可能性の高い場所を特定します。 実際のバグ。他の人は関連する木を歩いて情報源を特定します 悪いデータの。最も一般的なエラーには詳細な手順が含まれています 場合によってはエラーを回避するためのサンプルコードやリンクも含まれます さらなるドキュメントへ。

リアクティブパラダイム

変更可能なツリーベースの API は、次のような二分的なアクセス パターンに悩まされます。 ツリーの元の状態を作成するには、通常、まったく異なるメソッドが使用されます。 以降の更新よりも一連の操作が必要になります。 Flutterのレンダリングレイヤー 永続ツリーを維持する効果的な方法であるため、このパラダイムを使用します。 これは効率的なレイアウトとペイントの鍵となります。ただし、それが意味するのは、 レンダリング レイヤとの直接対話は、せいぜいぎこちないものである そして最悪の場合はバグが発生しやすくなります。

Flutter のウィジェット レイヤーには、 リアクティブパラダイム7を操作する 基礎となるレンダリング ツリー。 この API は、ツリーを結合することでツリー操作を抽象化します。 作成とツリーの変更ステップが単一のツリー記述 (ビルド) に組み込まれます。 ステップでは、システム状態が変更されるたびに、新しい構成が ユーザーインターフェイスの説明は開発者とフレームワークによって行われます。 この新しい変更を反映するために必要な一連のツリーの突然変異を計算します。 構成。

補間

Flutter のフレームワークでは、開発者がインターフェースを記述することが推奨されているため、 現在のアプリケーションの状態に一致する構成、メカニズムが存在する これらの構成間で暗黙的にアニメーション化します。

たとえば、状態 S で次のように仮定します。1インターフェースは以下で構成されます 円ですが、状態は S です2それは正方形で構成されています。 アニメーション メカニズムがないと、状態の変化に不快感が生じる可能性があります。 インターフェースの変更。暗黙的なアニメーションにより、円を滑らかに描くことができます。 複数のフレームにわたって正方形になります。

暗黙的にアニメーション化できる各機能には、ステートフルなウィジェットがあります。 入力の現在の値を記録し、アニメーションを開始します。 入力値が変化するたびに、現在から遷移するシーケンス 指定された期間にわたって値を新しい値に戻します。

これは次を使用して実装されますlerp(線形補間)関数を使用した 不変オブジェクト。各状態 (この場合は円と四角) で構成される不変オブジェクトとして表されます。 適切な設定 (色、ストロークの幅など) とペイント方法を知っている 自体。アニメーション中に中間ステップを描画するとき、 開始値と終了値は適切な値に渡されます。lerp関数 と一緒にtアニメーションに沿った点を表す値、 ここで、0.0 はstart1.0 はend8、 そして関数は、 中間段階。

円から四角への移行の場合、lerp関数は返すでしょう 次のように記述される半径を持つ「丸い正方形」を表すオブジェクト から導かれる分数t値、を使用して補間された色lerpカラーの関数と、lerpダブルス用の機能です。そのオブジェクトは、 円や四角と同じインターフェースでペイントできるようになります。 要求された場合にはそれ自体。

この技術により、状態機構、つまり状態のマッピングが可能になります。 構成、アニメーション機構、補間機構、 そして、各フレームをペイントする方法に関連する特定のロジック お互いに完全に分離されています。

このアプローチは広く適用可能です。 Flutter では、次のような基本的な型ColorShape補間することもできますが、さらに複雑にすることもできます などのタイプDecorationTextStyle、 またTheme。これらは 通常、それ自体が補間可能なコンポーネントから構築されます。 より複雑なオブジェクトの補間は、多くの場合次のように単純です。 複雑な要素を記述するすべての値を再帰的に補間します。 オブジェクト。

一部の補間可能なオブジェクトは、クラス階層によって定義されます。例えば、 形状は次のように表されます。ShapeBorderインターフェースがあり、 さまざまな形状を含むBeveledRectangleBorderBoxBorderCircleBorderRoundedRectangleBorder、 とStadiumBorder。独身者lerp関数は考えられるすべての型を予測することはできません。 したがって、インターフェイスは代わりに次のように定義しますlerpFromlerpToメソッド、 静的なものlerp方法はに準拠します。から補間するように指示されたとき 形状 A から形状 B へ、最初に B ができるかどうか尋ねられます。lerpFromA、それでは、 それができない場合、A は代わりにできるかどうかを尋ねられます。lerpToB. (どちらでもない場合 可能であれば、関数は次の値から A を返します。t0.5未満、 それ以外の場合は B を返します。)

これにより、クラス階層を任意に拡張できます。 加算により、既知の値の間を補間できる そして彼ら自身も。

場合によっては、補間自体を次のいずれかで記述できない場合があります。 利用可能なクラスを記述し、プライベート クラスが定義されています。 中間段階。これは、たとえば補間する場合に当てはまります。 の間CircleBorderそしてRoundedRectangleBorder

このメカニズムにはさらにもう 1 つの利点があります。それは、補間を処理できることです。 中間段階から新たな価値観へ。例えば途中で 円から四角への移行、形状をもう一度変更することができます。 そのため、アニメーションを三角形に補間する必要があります。限り 三角形クラスは次のことができますlerpFrom丸みを帯びた正方形の中間クラス、 移行はシームレスに実行できます。

結論

Flutter のスローガン「すべてはウィジェットである」は、構築を中心に展開しています。 ユーザー インターフェイスは、ウィジェットを構成することによって作成されます。 徐々に基本的なウィジェットが増えていきます。この攻撃的な結果は、 構成には慎重な作業が必要な多数のウィジェットがあります 効率的に処理できるように設計されたアルゴリズムとデータ構造。 いくつかの追加設計を行うことで、これらのデータ構造を使用することもできます。 開発者は、構築する無限スクロールリストを簡単に作成できます。 ウィジェットが表示されるとオンデマンドで表示されますe.


脚注:

1少なくともレイアウトに関しては。見直されるかも知れません ペイント用、必要に応じてアクセシビリティ ツリーの構築用、 必要に応じてヒットテストを行います。

2もちろん、現実はもう少しあります 複雑。一部のレイアウトには固有の寸法またはベースラインが含まれます 測定には、関連するサブツリーの追加のウォークが含まれます。 (積極的なキャッシュは、二次関数の可能性を軽減するために使用されます。 最悪の場合のパフォーマンスが低下します)。しかし、こうしたケースは驚くべきことに、 レア。特に、固有のディメンションは必要ありません。 よくあるシュリンク包装のケース。

3厳密に言えば、子供の立場はそうではありません。 RenderBox ジオメトリの一部であるため、実際には必要ありません。 レイアウト時に計算されます。多くのレンダリング オブジェクトは暗黙的に配置されます。 自身の原点を基準にして 0,0 にある単一の子。 計算やストレージはまったく必要ありません。一部のレンダリング オブジェクト 最後まで子供の位置を計算しないようにする 可能性のある瞬間(たとえば、塗装段階中)を避けるために、 その後ペイントされない場合、計算は完全に終了します。

4この規則には例外が 1 つあります。 で議論されているように、オンデマンドでウィジェットを構築するセクションでは、レイアウトの変更の結果として一部のウィジェットが再構築される可能性があります 制約。ウィジェットが無関係な理由でそれ自体をダーティとしてマークした場合、 同じフレームでもレイアウト制約の変更による影響を受けます。 2回更新されます。この冗長ビルドは以下に限定されます。 ウィジェット自体には影響を与えず、その子孫には影響しません。

5キーはオプションで不透明なオブジェクトです 等価演算子が影響を与えるために使用されるウィジェットに関連付けられています 調整アルゴリズム。

6アクセシビリティのため、およびアプリケーションを提供するため ウィジェットが構築されてからウィジェットが構築されるまでの追加の数ミリ秒 が画面に表示され、ビューポートが作成されます (ただしペイントはされません)。 表示されるウィジェットの前後の数百ピクセルのウィジェット。

7このアプローチを最初に普及させたのは、 Facebook の React ライブラリ。

8実際には、t値は許可されます 0.0 ~ 1.0 の範囲を超えて拡張し、一部のカーブで拡張します。ために たとえば、「弾性」曲線は、次のことを表現するために一時的にオーバーシュートします。 弾むような効果。補間ロジックは通常、次のように推定できます。 必要に応じて、開始または終了を過ぎてください。一部のタイプでは、たとえば、 色を補間するとき、t値は効果的にクランプされます 0.0 ~ 1.0 の範囲。